"""
Coordinate related stuff, use pygame axis
"""

"""
pygame axis:
                     x
    +---------------->
    |               ^
    |               |
    |               | height
    |<--- width --->|
    |               V
  y V
"""
from math import cos, sin
from typing import Any, Literal, Self, TypeAlias, overload

import pygame

from vengine.ve_exceptions import _type_checker


class Point2d:
    """ Represent a single point on the window """

    @overload
    def __init__(self, __x: float, __y: float): ...
    @overload
    def __init__(self, __coordinate: "T_Coordinate"): ...
    def __init__(self, *args):
        assert len(args) <= 2, ValueError("Inappropriate argument")
        
        self.__x: float = 0
        self.__y: float = 0
        
        if len(args) == 2:
            self.x, self.y = args[0], args[1]
            return
        
        __coordinate = _type_check_coordinate(args[0])
        self.x, self.y = __coordinate[0], __coordinate[1]
                        
    @property
    def x(self) -> float:
        return self.__x
    
    @x.setter
    def x(self, value: float | int):
        self.__x = float(_type_checker(value, int, float))
        
    @property
    def y(self) -> float:
        return self.__y
    
    @y.setter
    def y(self, value: float | int):
        self.__y = float(_type_checker(value, int, float))
        
    def __getitem__(self, value: Literal[0, 1]) -> float:
        if value == 0: return self.__x
        if value == 1: return self.__y
        raise ValueError(f"Expect 0 or 1, got {value}")

    def __add__(self, other: "T_Coordinate") -> Self:
        other = _type_check_coordinate(other)
        return self.__class__(
            self.__x + other[0],
            self.__y + other[1]
        )
    
    def __sub__(self, other: "T_Coordinate") -> Self:
        other = _type_check_coordinate(other)
        return self.__class__(
            self.__x - other[0],
            self.__y - other[1]
        )
        
    def __str__(self) -> str:
        return f"Point2d({self.__x}, {self.__y})"
    
    def __repr__(self) -> str:
        return f"Point2d({self.__x}, {self.__y})"
    
    def distance_to(self, other: "T_Coordinate") -> float:
        """ Point to point euclidean distance """
        other = _type_check_coordinate(other)
        return pow(
            abs(self.__x - other[0]) ** 2 + abs(self.__y - other[1]) ** 2,
            0.5
        )
        
    def to_pygame(self) -> tuple[float, float]:
        return (self.__x, self.__y)

class Vec2d:
    """ Represent a vector """
    
    @overload
    def __init__(self, __coord: "T_Coordinate" | Self) -> None: ...
    @overload
    def __init__(self, __rad: float, __norm: float) -> None: ...
    def __init__(self, *args):
        assert len(args) <= 2, ValueError("Inappropriate argument")
        
        self.__x: float = 0
        self.__y: float = 0
        
        if len(args) == 1:
            __coord = _type_check_coordinate(args[0])
            self.x = __coord[0]
            self.y = __coord[1]
            return
        
        __rad = _type_checker(args[0], float)
        __norm = _type_checker(args[1], float)
        self.x = __norm * cos(__rad)
        self.y = __norm * sin(__rad)

    @property
    def x(self) -> float:
        return self.__x
    
    @x.setter
    def x(self, value: float | int):
        self.__x = float(_type_checker(value, int, float))
        
    @property
    def y(self) -> float:
        return self.__y
    
    @y.setter
    def y(self, value: float | int):
        self.__y = float(_type_checker(value, int, float))
        
    def __getitem__(self, value: Literal[0, 1]) -> float:
        if value == 0: return self.__x
        if value == 1: return self.__y
        raise ValueError(f"Expect 0 or 1, got {value}")
    
    def __add__(self, other: "T_Coordinate") -> Self:
        other = _type_check_coordinate(other)
        return self.__class__(
            self.__x + other[0],
            self.__y + other[1]
        )
    
    def __sub__(self, other: "T_Coordinate") -> Self:
        other = _type_check_coordinate(other)
        return self.__class__(
            self.__x - other[0],
            self.__y - other[1]
        )

    def __str__(self) -> str:
        return f"Vec2d({self.__x}, {self.__y})"
    
    def __repr__(self) -> str:
        return f"Vec2d({self.__x}, {self.__y})"

    @property
    def norm(self) -> float:
        return pow(
            self.__x ** 2 + self.__y ** 2,
            0.5
        )

class Area2d:
    """ Represent a rectangle area on the window """
    
    @overload
    def __init__(self, __rect: pygame.Rect | Self) -> None: ...
    @overload
    def __init__(self, __topleft: "T_Coordinate", __bottomright: "T_Coordinate") -> None: ...
    # @overload
    # def __init__(self, __center: T_Coordinate, __width: int, __height: int) -> None: ...
    def __init__(self, *args):
        assert len(args) <= 2, ValueError("Inappropriate argument")
        
        if len(args) == 1:
            __rect = _type_checker(args[0], pygame.Rect, self.__class__)
            self.__topleft = Point2d(__rect.topleft)
            self.__bottomright = Point2d(__rect.bottomright)
            
        elif len(args) == 2:
            __topleft = _type_check_coordinate(args[0])
            __bottomright = _type_check_coordinate(args[1])
            self.__topleft = Point2d(__topleft)
            self.__bottomright = Point2d(__bottomright)
            
        self.__topright = Point2d(self.__bottomright.x, self.__topleft.y)
        self.__bottomleft = Point2d(self.__topleft.x, self.__bottomright.y)
        self.__width: float = self.__topright.x - self.__topleft.x
        self.__height: float = self.__bottomleft.y - self.__topleft.y
        self.__center = Point2d(
            self.__topleft.x + self.__width / 2,
            self.__topleft.y + self.__height / 2
        )
        self.__left: float = self.__topleft.x
        self.__right: float = self.__topright.x
        self.__top: float = self.__topleft.y
        self.__bottom: float = self.__bottomleft.y
        
    @property
    def topleft(self) -> Point2d: return self.__topleft
    @property
    def topright(self) -> Point2d: return self.__topright
    @property
    def bottomleft(self) -> Point2d: return self.__bottomleft
    @property
    def bottomright(self) -> Point2d: return self.__bottomright
    @property
    def center(self) -> Point2d: return self.__center
    @property
    def width(self) -> float: return self.__width
    @property
    def height(self) -> float: return self.__height
    @property
    def left(self) -> float: return self.__left
    @property
    def right(self) -> float: return self.__right
    @property
    def top(self) -> float: return self.__top
    @property
    def bottom(self) -> float: return self.__bottom
    
    def __str__(self) -> str:
        return f"Area2d(<{self.__topleft.x}, {self.topleft.y}> <{self.bottomright.x}, {self.bottomright.y}>)"
    
    def __repr__(self) -> str:
        return f"Area2d(<{self.__topleft.x}, {self.topleft.y}> <{self.bottomright.x}, {self.bottomright.y}>)"
    
    def __contains__(self, point: Point2d) -> bool:
        return (self.left <= point.x <= self.right) and (self.top <= point.y <= self.bottom)
    
    def to_pygame(self) -> pygame.Rect:
        return pygame.Rect(
            self.__left,
            self.__top,
            self.__width,
            self.__height
        )
        
T_Coordinate: TypeAlias = tuple[float, float] | list[float] | pygame.Vector2 | Point2d | Vec2d
def _type_check_coordinate(obj: Any) -> T_Coordinate:
    assert hasattr(obj, "__getitem__"), TypeError(f"Expect {T_Coordinate}, got {type(obj)}")
    return obj